package camera

import (
	"bytes"
	"errors"
	"fmt"
	"image"
	"image/jpeg"
	"os"
	"path/filepath"
	"sort"
	"sync"
	"time"

	"github.com/blackjack/webcam"
)

const (
	// MJPEG图像格式
	PixelFormatMjpeg = webcam.PixelFormat(0x47504a4d)
	// 取流失败重试次数
	GetStreamRetryCount = 50
)

// AsyncThreadResult 异步线程响应结果
type AsyncThreadResult struct {
	Err   error  // 异常信息
	Bytes []byte // 拷贝后的图像流
}

// Control 相机控制器
type Control struct {
	rwmutex            sync.RWMutex            // 读写锁
	deviceCacheList    CameraInfoList          // 缓存的相机信息列表
	deviceHandle       *webcam.Webcam          // 当前使用的设备
	deviceInfo         CameraInfo              // 当前使用的相机信息
	deviceSupportInfo  CameraSupportInfo       // 当前使用的相机支持信息
	flushStreamWG      sync.WaitGroup          // 刷流线程等待组
	closeSignalChan    chan struct{}           // 相机关闭请求信号（必须为无缓冲区的管道）
	latestImgWriteChan chan struct{}           // 最新图像写信号（必须为无缓冲区的管道）
	latestImgReadChan  chan *AsyncThreadResult // 最新图像读信号（必须为无缓冲区的管道）
}

// NewControl 创建一个相机控制器
func NewControl() *Control {
	return &Control{
		closeSignalChan:    make(chan struct{}),
		latestImgWriteChan: make(chan struct{}),
		latestImgReadChan:  make(chan *AsyncThreadResult),
	}
}

func getDeviceInfo(path string, id int) (*CameraBaseInfo, error) {
	// 打开设备
	webcamCam, err := webcam.Open(path)
	if err != nil {
		return nil, err
	}
	defer webcamCam.Close()

	// 检查设备是否支持mjpeg
	formats := webcamCam.GetSupportedFormats()
	if _, ok := formats[PixelFormatMjpeg]; !ok {
		return nil, errors.New("the device does not support MJPEG format")
	}

	// 获取相机信息
	name, _ := webcamCam.GetName()
	busInfo, _ := webcamCam.GetBusInfo()
	// 构建相机基础信息
	camera := &CameraBaseInfo{
		ID:              id,
		Name:            name,
		SymbolicLink:    path,
		LocationInfo:    busInfo,
		SupportInfoList: CameraSupportInfoList{},
		SupportInfo:     *CameraDefaultSupportInfo.Clone(),
	}

	// 过滤重复的相机信息
	filterRepeatSupportInfo := map[string]struct{}{}
	// 提取MJPEG支持的所有分辨率
	sizes := webcamCam.GetSupportedFrameSizes(PixelFormatMjpeg)
	for _, item := range sizes {
		// 过滤width小于600px的分辨率
		if item.MaxWidth < 600 {
			continue
		}
		// 获取分辨率支持的帧率
		rates := webcamCam.GetSupportedFramerates(PixelFormatMjpeg, item.MaxWidth, item.MaxHeight)
		for _, v := range rates {
			// 丢弃20帧以下的帧率
			if v.MaxDenominator/v.MaxNumerator < 20 {
				continue
			}
			// 是否已存在
			key := fmt.Sprintf("%d-%d-%d", item.MaxWidth, item.MaxHeight, v.MaxDenominator/v.MaxNumerator)
			if _, ok := filterRepeatSupportInfo[key]; ok {
				continue
			}
			// 缓存KEY
			filterRepeatSupportInfo[key] = struct{}{}
			// 追加支持信息
			camera.SupportInfoList = append(camera.SupportInfoList, &CameraSupportInfo{
				Width:  item.MaxWidth,
				Height: item.MaxHeight,
				FPS:    v.MaxDenominator / v.MaxNumerator,
			})
		}
	}

	// 对支持信息进行排序
	sort.Slice(camera.SupportInfoList, func(i, j int) bool {
		// 宽度是否相等
		if camera.SupportInfoList[i].Width == camera.SupportInfoList[j].Width {
			// 高度是否相等
			if camera.SupportInfoList[i].Height == camera.SupportInfoList[j].Height {
				// 按帧率从大到小排序
				return camera.SupportInfoList[i].FPS >= camera.SupportInfoList[j].FPS
			}
			// 按高度从大到小排序
			return camera.SupportInfoList[i].Height > camera.SupportInfoList[j].Height
		}
		// 按宽度从大到小排序
		return camera.SupportInfoList[i].Width > camera.SupportInfoList[j].Width
	})

	// OK
	return camera, nil
}

func discover(list CameraInfoList, discovered map[string]struct{}, pattern string) CameraInfoList {
	devices, err := filepath.Glob(pattern)
	if err != nil {
		// No v4l device.
		return list
	}

	for _, device := range devices {
		label := filepath.Base(device)
		reallink, err := os.Readlink(device)
		if err != nil {
			reallink = label
		} else {
			reallink = filepath.Base(reallink)
		}
		if _, ok := discovered[reallink]; ok {
			continue
		}

		discovered[reallink] = struct{}{}
		cameraBaseInfo, err := getDeviceInfo(device, len(list)+1)
		if err != nil {
			fmt.Fprintln(os.Stderr, err.Error())
			continue
		}

		list = append(list, &CameraInfo{
			//ID:             uint(len(list) + 1),
			CameraBaseInfo: *cameraBaseInfo,
		})
	}

	// OK
	return list
}

// GetList 获取相机列表
//
//	@return 相机列表
//	@return 错误信息
func (p *Control) GetList() (CameraInfoList, error) {
	// 操作加锁
	p.rwmutex.Lock()
	defer p.rwmutex.Unlock()

	// 清除缓存
	p.deviceCacheList = nil
	// 过滤重复相机
	discovered := make(map[string]struct{})
	// 查询设备列表
	p.deviceCacheList = discover(p.deviceCacheList, discovered, "/dev/v4l/by-id/*")
	p.deviceCacheList = discover(p.deviceCacheList, discovered, "/dev/v4l/by-path/*")
	p.deviceCacheList = discover(p.deviceCacheList, discovered, "/dev/video*")
	// 判断设备数量
	if len(p.deviceCacheList) == 0 {
		return nil, nil
	}

	// 返回相机克隆列表
	return p.deviceCacheList.Clone(), nil
}

// GetInfoByID 通过相机ID获取缓存的相机信息
//
//	@var	id	相机ID
//	@return	缓存的相机信息
//	@return	异常信息
func (p *Control) GetInfoByID(id uint) (*CameraInfo, error) {
	// 加读锁
	p.rwmutex.RLock()
	defer p.rwmutex.RUnlock()
	// 执行查找
	return p.deviceCacheList.Get(id)
}

// GetInfoByID 通过相机唯一固定值获取缓存的相机信息
//
//	@var	val	相机唯一固定值
//	@return	缓存的相机信息
//	@return	异常信息
func (p *Control) GetByUniqueFixedValue(val string) (*CameraInfo, error) {
	// 加读锁
	p.rwmutex.RLock()
	defer p.rwmutex.RUnlock()
	// 执行查找
	return p.deviceCacheList.GetByUniqueFixedValue(val)
}

// 发送最新图像写信号
func (p *Control) sendLatestImgWriteChan(timeout ...time.Duration) error {
	duration := time.Millisecond * 50
	if len(timeout) > 0 && timeout[0] > 0 {
		duration = timeout[0]
	}
	select {
	case p.latestImgWriteChan <- struct{}{}: // 信号发送成功
		return nil
	case <-time.After(duration): // 信号发送超时
		fmt.Fprintln(os.Stderr, ErrWriteChannelSendTimeout.Error())
		return ErrWriteChannelSendTimeout
	}
}

// 发送最新图像读信号
func (p *Control) sendLatestImgReadChan(data *AsyncThreadResult) error {
	select {
	case p.latestImgReadChan <- data: // 信号发送成功
		return nil
	case <-time.After(time.Millisecond * 50): // 信号发送超时
		fmt.Fprintln(os.Stderr, ErrReadChannelSendTimeout.Error())
		return ErrReadChannelSendTimeout
	}
}

// 发送相机关闭请求信号
func (p *Control) sendCloseSignalChan(timeout ...time.Duration) error {
	duration := time.Millisecond * 50
	if len(timeout) > 0 && timeout[0] > 0 {
		duration = timeout[0]
	}
	select {
	case p.closeSignalChan <- struct{}{}: // 信号发送成功
		return nil
	case <-time.After(duration): // 信号发送超时
		fmt.Fprintln(os.Stderr, ErrCloseStartChannelSendTimeout.Error())
		return ErrCloseStartChannelSendTimeout
	}
}

// 刷新相机流
//
//	@var	handle	临时相机句柄
func (p *Control) flushStream(handle *webcam.Webcam) {
	// 捕获异常
	defer func() {
		if e := recover(); e != nil {
			// 打印异常
			fmt.Fprintln(os.Stderr, "FlushStream Panic: ", e)
		}
		// 移除一个组员
		p.flushStreamWG.Done()
	}()

	// 死循环刷流
	for {
		// 等待Linux取流信号
		errs := handle.WaitForFrame(5)
		// 等待信号
		select {
		// 请求关闭相机
		case <-p.closeSignalChan:
			// 结束循环
			return

		// 取流信号
		case <-p.latestImgWriteChan:
			func() {
				// 取流是否异常
				if errs != nil {
					// 发送取流结果信号
					_ = p.sendLatestImgReadChan(&AsyncThreadResult{
						Err: errs,
					})
					return
				}

				// 读取一帧
				buf, index, errs := handle.GetFrame()
				// 取流是否异常
				if errs != nil {
					_ = p.sendLatestImgReadChan(&AsyncThreadResult{
						Err: errs,
					})
					return
				}
				// 延迟释放
				defer handle.ReleaseFrame(index)

				// 拷贝流
				copyBuf := make([]byte, len(buf))
				wn := copy(copyBuf, buf)
				if wn != len(buf) {
					_ = p.sendLatestImgReadChan(&AsyncThreadResult{
						Err: ErrCopyStreamFail,
					})
					return
				}
				// 拷贝OK
				_ = p.sendLatestImgReadChan(&AsyncThreadResult{
					Err:   nil,
					Bytes: copyBuf,
				})
			}()

		// 默认
		default:
			if errs != nil {
				// 打印异常
				fmt.Fprintln(os.Stderr, "WaitForFrame: "+errs.Error())
				continue
			}
			// 读一帧，扔掉
			_, _ = handle.ReadFrame()
		}
	}
}

// Open 打开相机
//
//	@var	id		相机ID
//	@var	info	分辨率信息
//	@return	异常信息
func (p *Control) Open(id uint, info CameraSupportInfo) error {
	// 操作加锁
	p.rwmutex.Lock()
	defer p.rwmutex.Unlock()

	// 查询ID对应的相机信息
	cameraInfo, err := p.deviceCacheList.Get(id)
	if err != nil {
		return err
	}

	// 打开新的相机
	tmpHandle, err := webcam.Open(cameraInfo.SymbolicLink)
	if err != nil {
		return errors.Join(ErrOpenFail, err)
	}
	defer func() {
		// 后续操作是否存在异常
		if err != nil {
			// 关闭相机
			tmpHandle.Close()
		}
	}()

	// 是否需要修改分辨率
	if info.Width > 0 && info.Height > 0 && info.FPS > 0 {
		// 筛选分辨率
		sInfo, err := cameraInfo.SupportInfoList.Get(info)
		if err != nil {
			return err
		}
		// 赋值选择的分辨率
		_, w, h, err := tmpHandle.SetImageFormat(PixelFormatMjpeg, sInfo.Width, sInfo.Height)
		if err != nil {
			return errors.Join(ErrSetSupportInfoFail, err)
		}
		err = tmpHandle.SetFramerate(float32(sInfo.FPS))
		if err != nil {
			return errors.Join(ErrSetSupportInfoFail, err)
		}
		fps, err := tmpHandle.GetFramerate()
		if err != nil {
			return errors.Join(ErrGetCurrConfigInfoFail, err)
		}
		// 赋值配置后的结果
		info = CameraSupportInfo{
			Width:  w,
			Height: h,
			FPS:    uint32(fps),
		}
	} else if len(cameraInfo.SupportInfoList) > 0 {
		// 选中第一个分辨率
		sInfo := cameraInfo.SupportInfoList[0]
		// 赋值选择的分辨率
		_, w, h, err := tmpHandle.SetImageFormat(PixelFormatMjpeg, sInfo.Width, sInfo.Height)
		if err != nil {
			return errors.Join(ErrSetSupportInfoFail, err)
		}
		err = tmpHandle.SetFramerate(float32(sInfo.FPS))
		if err != nil {
			return errors.Join(ErrSetSupportInfoFail, err)
		}
		fps, err := tmpHandle.GetFramerate()
		if err != nil {
			return errors.Join(ErrGetCurrConfigInfoFail, err)
		}
		// 赋值配置后的结果
		info = CameraSupportInfo{
			Width:  w,
			Height: h,
			FPS:    uint32(fps),
		}
	}

	// 开启取流线程
	err = tmpHandle.StartStreaming()
	if err != nil {
		return errors.Join(ErrStartStreamingFail, err)
	}

	// 赋值句柄
	p.deviceHandle = tmpHandle
	// 赋值当前使用的相机信息
	p.deviceInfo = *cameraInfo
	// 忘刷流线程分组增加一个组员
	p.flushStreamWG.Add(1)
	// 异步循环刷新缓冲区
	go p.flushStream(tmpHandle)

	// 最大连续取流50次，有一次成功就可以
	for i := 0; i < 50; i++ {
		// 发送一次取流信号
		err = p.sendLatestImgWriteChan(time.Second * 5)
		if err != nil {
			continue
		}
		// 等待相机就绪
		select {
		// 相机取流成功
		case imgRes := <-p.latestImgReadChan:
			if imgRes.Err != nil {
				err = imgRes.Err
				continue
			}
			// 转码JPEG图像
			var imgConf image.Config
			imgConf, err = jpeg.DecodeConfig(bytes.NewReader(imgRes.Bytes))
			if err != nil {
				continue
			}
			// 赋值图像宽高
			p.deviceSupportInfo.Width = uint32(imgConf.Width)
			p.deviceSupportInfo.Height = uint32(imgConf.Height)
			p.deviceSupportInfo.FPS = info.FPS
			// OK
			return nil

		// 相机取流超时
		case <-time.After(time.Second * 3):
			err = ErrReadChannelRecvTimeout
			continue
		}
	}

	// 返回异常状态
	return err
}

// GetInfo 获取当前设备的信息
//
//	@return	当前设备的信息
//	@return	异常信息
func (p *Control) GetInfo() (string, error) {
	// 操作加读锁
	p.rwmutex.RLock()
	defer p.rwmutex.RUnlock()

	// 检查相机是否已打开
	if p.deviceHandle == nil {
		return "", ErrNotOpen
	}

	// OK
	return fmt.Sprintf(
		"%s %dx%d(%d)",
		p.deviceInfo.Name,
		p.deviceSupportInfo.Width,
		p.deviceSupportInfo.Height,
		p.deviceSupportInfo.FPS,
	), nil
}

// GetSupportInfo 获取当前设备的支持信息
//
//	@return	当前设备的支持信息
//	@return	异常信息
func (p *Control) GetSupportInfo() (*CameraSupportInfo, error) {
	// 操作加读锁
	p.rwmutex.RLock()
	defer p.rwmutex.RUnlock()

	// 检查相机是否已打开
	if p.deviceHandle == nil {
		return nil, ErrNotOpen
	}

	// 检查分辨率是否存在
	if p.deviceSupportInfo.Width <= 0 || p.deviceSupportInfo.Height <= 0 {
		return nil, ErrGetCurrConfigInfoFail
	}

	// 返回结果
	return &CameraSupportInfo{
		Width:  p.deviceSupportInfo.Width,
		Height: p.deviceSupportInfo.Height,
		FPS:    p.deviceSupportInfo.FPS,
	}, nil
}

// GetStream 获取图片流
//
//	@return	图片流
//	@return	异常信息
func (p *Control) GetStream() ([]byte, error) {
	// 操作加锁
	p.rwmutex.Lock()
	defer p.rwmutex.Unlock()

	// 检查相机是否已打开
	if p.deviceHandle == nil {
		return nil, ErrNotOpen
	}

	// 执行取流，并做好取流失败重试准备
	for i := 1; i <= GetStreamRetryCount; i++ {
		// 发送写信号
		err := p.sendLatestImgWriteChan()
		if err != nil {
			// 是否需要尝试再次取流
			if i < GetStreamRetryCount {
				continue
			}
			return nil, err
		}

		// 等待取流完成或者取流超时
		select {
		case res := <-p.latestImgReadChan: // 取流完成信号
			// 是否异常
			if res.Err != nil {
				// 是否需要尝试再次取流
				if errors.Is(res.Err, new(webcam.Timeout)) && i < GetStreamRetryCount {
					continue
				}
				// Error
				return nil, res.Err
			}
			// OK
			return res.Bytes, nil
		case <-time.After(time.Millisecond * 50): // 取流超时信号
			// 是否需要尝试再次取流
			if i < GetStreamRetryCount {
				continue
			}
			// Timeout
			return nil, ErrReadChannelRecvTimeout
		}
	}

	// Timeout
	return nil, ErrReadChannelRecvTimeout
}

// Close 关闭已打开的相机
func (p *Control) Close() {
	// 操作加锁
	p.rwmutex.Lock()
	defer p.rwmutex.Unlock()

	// 是否存在已打开的相机
	if p.deviceHandle == nil {
		return
	}

	// 通知需要关闭相机
	_ = p.sendCloseSignalChan(time.Second * 5)
	// 等待所有刷流线程关闭
	p.flushStreamWG.Wait()

	// 关闭已打开的相机
	err := p.deviceHandle.Close()
	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
	}
	// 给内核一点时间
	time.Sleep(time.Millisecond * 100)
	// 清空句柄
	p.deviceHandle = nil
	// 清除当前使用的相机信息
	p.deviceInfo = CameraInfo{}
	p.deviceSupportInfo = CameraSupportInfo{}
}
